import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
import struct
import threading
import datetime

class SerialCommunicator:
    def __init__(self, root):
        self.root = root
        self.root.title("串口通信工具V0.02")
        self.root.geometry("800x400")
        self.root.resizable(True, True)
        
        self.ser = None
        self.is_connected = False
        # 添加状态变量用于跟踪拍照命令的发送状态
        self.is_waiting_response = False      # 是否等待响应
        self.is_restart_required = False      # 是否需要重启板子
        self.is_awaiting_0x70_for_capture = False  # 是否等待0x70命令后发送拍照
        self.has_sent_capture_after_0x70 = False   # 是否已经在收到0x70后发送了拍照命令
        # 添加状态变量用于跟踪录像命令的发送状态
        self.is_awaiting_0x70_for_record = False    # 是否等待0x70命令后发送录像
        self.has_sent_record_after_0x70 = False     # 是否已经在收到0x70后发送了录像命令
        # 添加状态变量用于抓拍命令
        self.is_awaiting_0x70_for_single_capture = False  # 是否等待0x70命令后发送抓拍
        self.has_sent_single_capture_after_0x70 = False   # 是否已经在收到0x70后发送了抓拍命令
        # 添加状态变量用于图像接收
        self.current_image_data = bytearray()  # 存储当前累积的图像数据
        self.cmd_73_counter = 0               # 统计收到的0x73命令数量
        self.is_collecting_image = True        # 默认开启累积收集模式
        self.image_total_chunks = 0            # 已收集的图像数据块数量
        self.image_saved_flag = False          # 图像是否已保存标志
        
        # 初始化时立即创建保存目录
        self._init_save_directory()
        
        self.setup_ui()
        self.update_ports()
        
    def _init_save_directory(self):
        """初始化时创建图像保存目录"""
        import os
        try:
            # 获取程序所在目录的绝对路径
            app_dir = os.path.dirname(os.path.abspath(__file__))
            self.save_dir = os.path.join(app_dir, "captured_images")
            
            # 创建目录
            if not os.path.exists(self.save_dir):
                os.makedirs(self.save_dir)
                print(f"已创建图像保存目录: {self.save_dir}")
            else:
                print(f"使用现有图像保存目录: {self.save_dir}")
        except Exception as e:
            print(f"创建图像保存目录时出错: {str(e)}")
            # 设置默认目录作为备用
            self.save_dir = os.path.join(os.getcwd(), "captured_images")
    
    def setup_ui(self):
        # 创建主框架
        main_frame = ttk.Frame(self.root, padding="20")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 串口设置区域
        port_frame = ttk.LabelFrame(main_frame, text="串口设置", padding="10")
        port_frame.pack(fill=tk.X, pady=10)
        
        ttk.Label(port_frame, text="端口:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
        self.port_var = tk.StringVar()
        self.port_combo = ttk.Combobox(port_frame, textvariable=self.port_var, state="readonly", width=15)
        self.port_combo.grid(row=0, column=1, padx=5, pady=5)
        
        ttk.Label(port_frame, text="波特率:").grid(row=0, column=2, sticky=tk.W, padx=5, pady=5)
        self.baud_var = tk.StringVar(value="1500000")
        baud_combo = ttk.Combobox(port_frame, textvariable=self.baud_var, state="readonly", width=12)
        baud_combo['values'] = ('9600', '19200', '38400', '57600', '115200', '1500000')
        baud_combo.grid(row=0, column=3, padx=5, pady=5)
        
        self.refresh_btn = ttk.Button(port_frame, text="刷新端口", command=self.update_ports)
        self.refresh_btn.grid(row=0, column=4, padx=5, pady=5)
        
        self.connect_btn = ttk.Button(port_frame, text="连接", command=self.toggle_connection)
        self.connect_btn.grid(row=0, column=5, padx=5, pady=5)
        
        # 命令发送区域
        cmd_frame = ttk.LabelFrame(main_frame, text="命令控制", padding="10")
        cmd_frame.pack(fill=tk.X, pady=10)
        
        # 拍照按钮
        self.capture_btn = ttk.Button(cmd_frame, text="拍照 (0x01)", command=self.send_capture_command, state=tk.DISABLED)
        self.capture_btn.pack(side=tk.LEFT, padx=10, pady=10)
        
        # 录像设置
        record_frame = ttk.Frame(cmd_frame)
        record_frame.pack(side=tk.LEFT, padx=10, pady=10)
        
        ttk.Label(record_frame, text="录像时间 (秒):").pack(side=tk.LEFT, padx=5)
        self.record_time_var = tk.StringVar(value="60")
        self.record_time_entry = ttk.Entry(record_frame, textvariable=self.record_time_var, width=5)
        self.record_time_entry.pack(side=tk.LEFT, padx=5)
        
        self.record_btn = ttk.Button(record_frame, text="录像 (0x02)", command=self.send_record_command, state=tk.DISABLED)
        self.record_btn.pack(side=tk.LEFT, padx=5)
        
        # 停止录像按钮
        self.stop_record_btn = ttk.Button(cmd_frame, text="停止录像 (0x08)", command=self.send_stop_record_command, state=tk.DISABLED)
        self.stop_record_btn.pack(side=tk.LEFT, padx=10, pady=10)
        
        # 恢复出厂设置按钮
        self.factory_reset_btn = ttk.Button(cmd_frame, text="恢复出厂设置 (0x13)", command=self.send_factory_reset_command, state=tk.DISABLED)
        self.factory_reset_btn.pack(side=tk.LEFT, padx=10, pady=10)
        
        # 开启AP按钮
        self.start_ap_btn = ttk.Button(cmd_frame, text="开启AP (0x16)", command=self.send_start_ap_command, state=tk.DISABLED)
        self.start_ap_btn.pack(side=tk.LEFT, padx=10, pady=10)
        
        # 抓拍按钮
        self.capture_single_btn = ttk.Button(cmd_frame, text="抓拍 (0x03)", command=self.send_capture_single_command, state=tk.DISABLED)
        self.capture_single_btn.pack(side=tk.LEFT, padx=10, pady=10)
        
        # 第二行命令框架
        cmd_frame2 = ttk.Frame(main_frame)
        cmd_frame2.pack(fill=tk.X, pady=(0, 10), padx=10)
        
        # 显示自动保存模式提示标签
        image_control_frame = ttk.Frame(cmd_frame2)
        image_control_frame.pack(side=tk.LEFT, padx=10, pady=10)
        
        # 配网命令
        wifi_config_frame = ttk.Frame(cmd_frame2)
        wifi_config_frame.pack(side=tk.LEFT, padx=10, pady=10)
        
        ttk.Label(wifi_config_frame, text="SSID: ").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
        self.ssid_var = tk.StringVar(value="")
        self.ssid_entry = ttk.Entry(wifi_config_frame, textvariable=self.ssid_var, width=15)
        self.ssid_entry.grid(row=0, column=1, padx=5, pady=5)
        
        ttk.Label(wifi_config_frame, text="密码: ").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
        self.wifi_password_var = tk.StringVar(value="")
        self.wifi_password_entry = ttk.Entry(wifi_config_frame, textvariable=self.wifi_password_var, width=15, show="*")
        self.wifi_password_entry.grid(row=1, column=1, padx=5, pady=5)
        
        self.wifi_config_btn = ttk.Button(wifi_config_frame, text="配网 (0x04)", command=self.send_wifi_config_command, state=tk.DISABLED)
        self.wifi_config_btn.grid(row=0, column=2, rowspan=2, padx=10, pady=5)
        
        # 状态栏
        self.status_var = tk.StringVar(value="就绪")
        status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        
        # 日志区域
        log_frame = ttk.LabelFrame(main_frame, text="通信日志", padding="10")
        log_frame.pack(fill=tk.BOTH, expand=True, pady=10)
        
        # 日志控制按钮
        log_control_frame = ttk.Frame(log_frame)
        log_control_frame.pack(fill=tk.X, pady=(0, 5))
        
        self.clear_log_btn = ttk.Button(log_control_frame, text="清空日志", command=self.clear_log)
        self.clear_log_btn.pack(side=tk.RIGHT)
        
        # 日志文本区域
        log_content_frame = ttk.Frame(log_frame)
        log_content_frame.pack(fill=tk.BOTH, expand=True)
        
        self.log_text = tk.Text(log_content_frame, height=10, wrap=tk.WORD)
        self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        scrollbar = ttk.Scrollbar(log_content_frame, command=self.log_text.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.log_text.config(yscrollcommand=scrollbar.set)
    
    def update_ports(self):
        ports = serial.tools.list_ports.comports()
        port_list = [port.device for port in ports]
        self.port_combo['values'] = port_list
        if port_list:
            self.port_var.set(port_list[0])
    
    def toggle_connection(self):
        if not self.is_connected:
            self.connect_serial()
        else:
            self.disconnect_serial()
    
    def connect_serial(self):
        try:
            port = self.port_var.get()
            baudrate = int(self.baud_var.get())
            
            self.ser = serial.Serial(
                port=port,
                baudrate=baudrate,
                timeout=0.01,  # 减小超时时间，提高响应速度
                write_timeout=1,  # 添加写超时
                dsrdtr=False,  # 禁用硬件流控制
                rtscts=False
            )
            
            if self.ser.is_open:
                self.is_connected = True
                self.connect_btn.config(text="断开")
                self.capture_btn.config(state=tk.NORMAL)
                self.record_btn.config(state=tk.NORMAL)
                self.stop_record_btn.config(state=tk.NORMAL)
                self.factory_reset_btn.config(state=tk.NORMAL)
                self.start_ap_btn.config(state=tk.NORMAL)
                self.capture_single_btn.config(state=tk.NORMAL)
                self.wifi_config_btn.config(state=tk.NORMAL)
                self.status_var.set(f"已连接到 {port} @ {baudrate} bps")
                self.log(f"串口连接成功: {port} @ {baudrate} bps")
                self.log("已启用自动保存模式：所有0x73命令的参数数据将被累积，并在收到0x3命令后自动保存为JPG文件")
                
                # 启动接收线程
                self.receive_thread = threading.Thread(target=self.receive_data, daemon=True)
                self.receive_thread.start()
        except Exception as e:
            messagebox.showerror("连接错误", f"无法连接到串口: {str(e)}")
            self.log(f"连接失败: {str(e)}")
    
    def disconnect_serial(self):
        try:
            # 重置状态变量
            self.is_waiting_response = False
            self.is_restart_required = False
            self.is_awaiting_0x70_for_capture = False
            self.has_sent_capture_after_0x70 = False
            self.is_awaiting_0x70_for_record = False
            self.has_sent_record_after_0x70 = False
            self.is_awaiting_0x70_for_single_capture = False
            self.has_sent_single_capture_after_0x70 = False
            
            # 重置累积图像数据
            self.current_image_data = bytearray()
            self.image_total_chunks = 0
            
            if self.ser and self.ser.is_open:
                self.ser.close()
            
            self.is_connected = False
            self.connect_btn.config(text="连接")
            self.capture_btn.config(state=tk.DISABLED)
            self.record_btn.config(state=tk.DISABLED)
            self.stop_record_btn.config(state=tk.DISABLED)
            self.factory_reset_btn.config(state=tk.DISABLED)
            self.start_ap_btn.config(state=tk.DISABLED)
            self.capture_single_btn.config(state=tk.DISABLED)
            self.wifi_config_btn.config(state=tk.DISABLED)
            self.status_var.set("已断开连接")
            self.log("串口已断开")
        except Exception as e:
            messagebox.showerror("断开错误", f"断开连接时发生错误: {str(e)}")
    
    def send_capture_command(self):
        """触发拍照流程，设置等待0x70命令的标志，收到0x70后才会发送实际的拍照命令"""
        # 重置相关标志
        self.is_restart_required = False
        self.has_sent_capture_after_0x70 = False
        
        # 设置等待0x70命令的标志
        self.is_awaiting_0x70_for_capture = True
        
        # 显示提示信息
        self.log("已触发拍照操作，等待收到0x70命令后发送拍照指令...")
    
    def _send_actual_capture_command(self):
        """实际发送拍照命令的方法，只发送一次"""
        # 先发送设置时间命令
        self._send_set_time_command()
        
        cmd = 0x01
        param_length = 0
        
        # 构建数据包: 帧头(0xaa) + 命令(0x01) + 参数长度(2字节，值为0) + 参数内容(无) + CRC(4字节0x00)
        packet = struct.pack('BBH', 0xAA, cmd, param_length)
        crc_bytes = b'\x00\x00\x00\x00'
        packet_with_crc = packet + crc_bytes
        
        try:
            if self.ser and self.ser.is_open:
                self.ser.write(packet_with_crc)
                self.log(f"发送拍照命令: {[hex(b) for b in packet_with_crc]}")
                self.is_waiting_response = True
            else:
                self.log("无法发送拍照命令：串口未连接")
        except Exception as e:
            self.log(f"发送拍照命令时出错: {str(e)}")
    
    # 移除_continuous_send_capture方法，不再需要持续发送拍照命令
    
    def send_record_command(self):
        """触发录像流程，设置等待0x70命令的标志，收到0x70后才会发送实际的录像命令"""
        # 重置相关标志
        self.is_restart_required = False
        self.has_sent_record_after_0x70 = False
        
        # 设置等待0x70命令的标志
        self.is_awaiting_0x70_for_record = True
        
        # 显示提示信息
        self.log("已触发录像操作，等待收到0x70命令后发送录像指令...")
    
    def _send_actual_record_command(self):
        """实际发送录像命令的方法，只发送一次"""
        # 先发送设置时间命令
        self._send_set_time_command()
        
        cmd = 0x02
        param_length = 4  # 4字节时间参数
        
        try:
            record_time = int(self.record_time_var.get())
            if record_time < 0 or record_time > 1024:
                self.log(f"录像时间参数错误: {record_time}秒，必须在0-1024秒之间")
                return
            
            # 构建数据包: 帧头(0xaa) + 命令(0x02) + 参数长度(2字节，值为4，大端格式) + CRC(4字节0x00) + 参数内容(时间)
            # 按照正确顺序：帧头 + 命令 + 参数长度 + CRC + 时间值
            # 使用大端格式打包参数长度
            base_packet = struct.pack('>BBH', 0xAA, cmd, param_length)
            # 添加CRC
            crc_bytes = b'\x00\x00\x00\x00'
            # 添加时间参数，使用小端格式
            time_bytes = struct.pack('<I', record_time)  # 使用小端格式
            # 组合成完整数据包
            packet_with_crc = base_packet + crc_bytes + time_bytes
            
            if self.ser and self.ser.is_open:
                self.ser.write(packet_with_crc)
                self.log(f"发送录像命令: {[hex(b) for b in packet_with_crc]}")
                self.is_waiting_response = True
            else:
                self.log("无法发送录像命令：串口未连接")
        except ValueError:
            self.log(f"录像时间参数错误: {self.record_time_var.get()} 不是有效的数字")
        except Exception as e:
            self.log(f"发送录像命令时出错: {str(e)}")
    
    def send_packet(self, packet, command_name):
        try:
            if self.ser and self.ser.is_open:
                # 添加4个字节的CRC（默认都为0x00）
                crc_bytes = b'\x00\x00\x00\x00'
                packet_with_crc = packet + crc_bytes
                
                self.ser.write(packet_with_crc)
                self.log(f"发送{command_name}命令: {[hex(b) for b in packet_with_crc]}")
            else:
                messagebox.showwarning("未连接", "请先连接串口")
        except Exception as e:
            messagebox.showerror("发送错误", f"发送命令时发生错误: {str(e)}")
            self.log(f"发送失败: {str(e)}")
    
    def send_stop_record_command(self):
        """发送停止录像命令"""
        cmd = 0x08
        param_length = 0
        
        # 构建数据包: 帧头(0xaa) + 命令(0x08) + 参数长度(2字节，值为0) + CRC(4字节0x00)
        packet = struct.pack('>BBH', 0xAA, cmd, param_length)  # 参数长度使用大端格式
        crc_bytes = b'\x00\x00\x00\x00'
        packet_with_crc = packet + crc_bytes
        
        try:
            if self.ser and self.ser.is_open:
                self.ser.write(packet_with_crc)
                self.log(f"发送停止录像命令: {[hex(b) for b in packet_with_crc]}")
                self.is_waiting_response = True
            else:
                self.log("无法发送停止录像命令：串口未连接")
        except Exception as e:
            self.log(f"发送停止录像命令时出错: {str(e)}")
    
    def send_factory_reset_command(self):
        """发送恢复出厂设置命令(0x13)，无参数"""
        cmd = 0x13
        param_length = 0
        
        # 构建数据包: 帧头(0xaa) + 命令(0x13) + 参数长度(2字节，值为0) + CRC(4字节0x00)
        packet = struct.pack('>BBH', 0xAA, cmd, param_length)  # 参数长度使用大端格式
        crc_bytes = b'\x00\x00\x00\x00'
        packet_with_crc = packet + crc_bytes
        
        try:
            if self.ser and self.ser.is_open:
                self.ser.write(packet_with_crc)
                self.log(f"发送恢复出厂设置命令: {[hex(b) for b in packet_with_crc]}")
                self.is_waiting_response = True
            else:
                self.log("无法发送恢复出厂设置命令：串口未连接")
        except Exception as e:
            self.log(f"发送恢复出厂设置命令时出错: {str(e)}")
    
    def send_start_ap_command(self):
        """发送开启AP命令(0x16)，无参数"""
        cmd = 0x16
        param_length = 0
        
        # 构建数据包: 帧头(0xaa) + 命令(0x16) + 参数长度(2字节，值为0) + CRC(4字节0x00)
        packet = struct.pack('>BBH', 0xAA, cmd, param_length)  # 参数长度使用大端格式
        crc_bytes = b'\x00\x00\x00\x00'
        packet_with_crc = packet + crc_bytes
        
        try:
            if self.ser and self.ser.is_open:
                self.ser.write(packet_with_crc)
                self.log(f"发送开启AP命令: {[hex(b) for b in packet_with_crc]}")
                self.is_waiting_response = True
            else:
                self.log("无法发送开启AP命令：串口未连接")
        except Exception as e:
            self.log(f"发送开启AP命令时出错: {str(e)}")
    
    def send_capture_single_command(self):
        """触发单张抓拍流程，设置等待0x70命令的标志，收到0x70后才会发送实际的抓拍命令"""
        # 重置相关标志
        self.is_restart_required = False
        self.has_sent_single_capture_after_0x70 = False
        
        # 设置等待0x70命令的标志
        self.is_awaiting_0x70_for_single_capture = True
        
        # 显示提示信息
        self.log("已触发单张抓拍操作，等待收到0x70命令后发送抓拍指令...")
        
    def send_wifi_config_command(self):
        """发送配网命令(0x04)，包含SSID和密码参数"""
        cmd = 0x04
        
        # 获取SSID和密码
        ssid = self.ssid_var.get().strip()
        password = self.wifi_password_var.get().strip()
        
        # 验证参数
        if not ssid:
            self.log("错误：SSID不能为空")
            return
        
        # 构建参数：SSID长度(1字节) + SSID字符串 + 密码长度(1字节) + 密码字符串
        ssid_bytes = ssid.encode('utf-8')
        password_bytes = password.encode('utf-8')
        
        # 计算总参数长度
        param_length = 1 + len(ssid_bytes) + 1 + len(password_bytes)
        
        # 构建数据包: 帧头(0xaa) + 命令(0x04) + 参数长度(2字节，大端格式) + CRC(4字节0x00) + 参数内容
        try:
            # 使用大端格式打包参数长度
            base_packet = struct.pack('>BBH', 0xAA, cmd, param_length)
            # 添加CRC
            crc_bytes = b'\x00\x00\x00\x00'
            # 添加参数：SSID长度 + SSID + 密码长度 + 密码
            param_bytes = struct.pack('B', len(ssid_bytes)) + ssid_bytes + struct.pack('B', len(password_bytes)) + password_bytes
            # 组合成完整数据包
            packet_with_crc = base_packet + crc_bytes + param_bytes
            
            if self.ser and self.ser.is_open:
                self.ser.write(packet_with_crc)
                self.log(f"发送配网命令(0x04): SSID='{ssid}', 密码='{'*'*len(password)}'")
                self.log(f"发送配网命令数据: {[hex(b) for b in packet_with_crc]}")
                self.is_waiting_response = True
            else:
                self.log("无法发送配网命令：串口未连接")
        except Exception as e:
            self.log(f"发送配网命令时出错: {str(e)}")
    
    def _send_actual_capture_single_command(self):
        """实际发送单张抓拍命令的方法，只发送一次，不需要发送设置时间命令"""
        cmd = 0x03
        param_length = 0
        
        # 构建数据包: 帧头(0xaa) + 命令(0x03) + 参数长度(2字节，值为0) + CRC(4字节0x00)
        packet = struct.pack('>BBH', 0xAA, cmd, param_length)  # 参数长度使用大端格式
        crc_bytes = b'\x00\x00\x00\x00'
        packet_with_crc = packet + crc_bytes
        
        try:
            if self.ser and self.ser.is_open:
                self.ser.write(packet_with_crc)
                self.log(f"发送单张抓拍命令: {[hex(b) for b in packet_with_crc]}")
                self.is_waiting_response = True
            else:
                self.log("无法发送单张抓拍命令：串口未连接")
        except Exception as e:
            self.log(f"发送单张抓拍命令时出错: {str(e)}")
            
    def _send_set_time_command(self):
        """发送设置时间命令(0x0C)，获取电脑当前时间并设置"""
        cmd = 0x0C
        param_length = 7  # int16 year + int8 month + int8 day + int8 hour + int8 min + int8 sec = 2+1+1+1+1+1=7字节
        
        # 获取电脑当前时间
        now = datetime.datetime.now()
        year = now.year  # int16
        month = now.month  # int8
        day = now.day  # int8
        hour = now.hour  # int8
        minute = now.minute  # int8
        second = now.second  # int8
        
        # 构建数据包: 帧头 + 命令 + 参数长度(大端格式) + CRC + 参数内容
        # 参数内容格式: int16 year, int8 month, int8 day, int8 hour, int8 min, int8 sec
        try:
            # 使用大端格式打包参数长度
            base_packet = struct.pack('>BBH', 0xAA, cmd, param_length)
            # 添加CRC
            crc_bytes = b'\x00\x00\x00\x00'
            # 添加时间参数，对于year手动调整字节顺序（交换高低字节）
            # 将year的字节顺序从大端改为小端格式
            year_low = year & 0xFF
            year_high = (year >> 8) & 0xFF
            # 对于其他参数保持大端格式
            time_bytes = struct.pack('BBbbbbb', year_low, year_high, month, day, hour, minute, second)
            # 组合成完整数据包
            packet_with_crc = base_packet + crc_bytes + time_bytes
            
            if self.ser and self.ser.is_open:
                self.ser.write(packet_with_crc)
                self.log(f"发送设置时间命令(0x0C): 年={year}, 月={month}, 日={day}, 时={hour}, 分={minute}, 秒={second}")
                self.log(f"发送设置时间命令数据: {[hex(b) for b in packet_with_crc]}")
            else:
                self.log("无法发送设置时间命令：串口未连接")
        except Exception as e:
            self.log(f"发送设置时间命令时出错: {str(e)}")
    
    def receive_data(self):
        """接收串口数据的线程函数，实现帧解析功能处理粘包情况"""
        buffer = bytearray()  # 缓冲区用于存储未处理的字节
        max_buffer_size = 1024 * 256  # 进一步增加最大缓冲区大小到256KB，以确保能容纳完整图像数据
        
        while self.is_connected:
            try:
                if self.ser and self.ser.is_open:
                    try:
                        # 优化读取策略：如果有等待数据，尽可能多地读取
                        if self.ser.in_waiting > 0:
                            # 根据等待数据量动态调整读取大小，但不超过4096字节以提高效率
                            read_size = min(4096, self.ser.in_waiting)
                            data = self.ser.read(read_size)
                            if data:
                                # 在添加新数据前检查缓冲区大小
                                if len(buffer) + len(data) > max_buffer_size:
                                    # 记录缓冲区即将溢出的情况
                                    self.log(f"警告：缓冲区接近最大容量({len(buffer)}字节)，尝试快速解析...")
                                    # 先尝试解析现有数据
                                    self.parse_frames(buffer)
                                    
                                buffer.extend(data)
                                # 尝试解析缓冲区中的帧，但对于0x73命令，我们会累积所有数据后再处理
                                self.parse_frames(buffer)
                    except (serial.SerialTimeoutException, IOError):
                        # 超时或IO错误，继续循环
                        pass
            except Exception as e:
                if self.is_connected:  # 只有在连接状态下才报错
                    self.log(f"接收错误: {str(e)}")
                    # 不要立即断开连接，继续尝试接收
                    pass
    
    def parse_frames(self, buffer):
        """解析缓冲区中的帧数据，每个帧以0xAA开头"""
        frames_processed = 0
        max_frames_per_call = 20  # 增加每次调用处理的最大帧数
        
        while len(buffer) > 0 and frames_processed < max_frames_per_call:
            # 查找0xAA帧头
            frame_start_index = buffer.find(0xAA)
            
            if frame_start_index == -1:
                # 没有找到帧头，但不立即清空缓冲区
                # 只在缓冲区过大时才清空，避免丢失可能的数据
                if len(buffer) > 1024 * 50:  # 进一步增加缓冲区阈值到50KB，尽量保留数据
                    self.log(f"缓冲区过大且无有效帧头，清空 {len(buffer)} 字节数据")
                    buffer.clear()
                break
            
            # 跳过帧头之前的所有数据 - 减少丢弃，只有在确实无意义时才记录
            if frame_start_index > 0:
                # 减少日志输出，避免影响性能
                if frame_start_index > 100:  # 只有当丢弃数据超过100字节时才记录日志，减少干扰
                    self.log(f"跳过 {frame_start_index} 字节前导数据")  # 改用"跳过"而不是"丢弃"，更准确描述
            
            # 确保有足够的数据来解析基本帧结构：帧头 + 命令 + 参数长度(2字节)
            if len(buffer) < 4:  # 0xAA + cmd(1字节) + param_length(2字节)
                break
            
            # 解析命令和参数长度
            cmd = buffer[1]
            param_length = (buffer[2] << 8) | buffer[3]  # 参数长度为2字节
            
            # 参数长度合理性检查，防止过大的长度值导致内存问题
            if param_length > 1024 * 256:  # 最大允许256KB的参数
                self.log(f"参数长度异常: {param_length} 字节，跳过当前帧")
                del buffer[0]  # 只删除帧头，继续寻找下一个有效帧头
                continue
            
            # 计算完整帧的长度：帧头(1) + 命令(1) + 参数长度(2) + 参数内容(param_length) + CRC(4)
            total_frame_length = 1 + 1 + 2 + param_length + 4
            
            if len(buffer) < total_frame_length:
                # 帧不完整，等待更多数据
                break
            
            # 提取完整的帧
            full_frame = buffer[:total_frame_length]
            
            # 从缓冲区删除已处理的帧
            del buffer[:total_frame_length]
            frames_processed += 1
            
            # 减少日志输出频率，特别是对于大量重复的帧
            if cmd != 0x73 and cmd != 0x74:  # 0x73和0x74命令不再打印完整帧内容
                self.log(f"接收完整帧(cmd=0x{cmd:02x}, length={total_frame_length})")
            elif frames_processed == 1:  # 每个批次只打印一次详细信息
                self.log(f"处理第{frames_processed}帧(cmd=0x{cmd:02x})")
            
            # 检查是否收到0x70命令
            if cmd == 0x70:
                self.log("收到0x70命令")
                
                # 如果正在等待0x70命令后发送拍照，并且还没有发送过，则发送一次拍照命令
                if self.is_awaiting_0x70_for_capture and not self.has_sent_capture_after_0x70:
                    self._send_actual_capture_command()
                    self.has_sent_capture_after_0x70 = True
                    self.is_awaiting_0x70_for_capture = False  # 完成后重置等待标志
                
                # 如果正在等待0x70命令后发送录像，并且还没有发送过，则发送一次录像命令
                if self.is_awaiting_0x70_for_record and not self.has_sent_record_after_0x70:
                    self._send_actual_record_command()
                    self.has_sent_record_after_0x70 = True
                    self.is_awaiting_0x70_for_record = False  # 完成后重置等待标志
                    
                # 如果正在等待0x70命令后发送单张抓拍，并且还没有发送过，则发送一次单张抓拍命令
                if self.is_awaiting_0x70_for_single_capture and not self.has_sent_single_capture_after_0x70:
                    self._send_actual_capture_single_command()
                    self.has_sent_single_capture_after_0x70 = True
                    self.is_awaiting_0x70_for_single_capture = False  # 完成后重置等待标志
            
            # 检查是否是拍照命令的响应
            elif cmd == 0x01:  # 拍照命令的响应
                # 重置等待响应标志
                self.is_waiting_response = False
                self.log("收到拍照命令响应")
                
            # 检查是否是单张抓拍命令的响应
            elif cmd == 0x03:  # 单张抓拍命令的响应
                # 重置等待响应标志
                self.is_waiting_response = False
                self.log("收到单张抓拍命令响应(0x03)")
                # 注意：现在图像保存由非1024长度的数据块触发，不再由0x03命令触发
                # 如果此时仍有未保存的数据，记录日志但不强制保存
                if len(self.current_image_data) > 0:
                    self.log(f"0x03命令：当前仍有累积数据（{len(self.current_image_data)}字节）")
                
                # 重置0x73命令计数器
                self.cmd_73_counter = 0
                self.log("已重置计数器，准备接收新的图像数据")
                
            # 检查是否是配网命令的响应
            elif cmd == 0x04:  # 配网命令的响应
                # 重置等待响应标志
                self.is_waiting_response = False
                self.log("收到配网命令响应")
                
            # 检查是否是停止录像命令的响应
            elif cmd == 0x08:  # 停止录像命令的响应
                # 重置等待响应标志
                self.is_waiting_response = False
                self.log("收到停止录像命令响应")
                
                # 检查是否是录像命令的响应
            elif cmd == 0x02:  # 录像命令的响应
                # 重置等待响应标志
                self.is_waiting_response = False
                self.log("收到录像命令响应")
                
                # 处理图像数据命令
            elif cmd == 0x73:  # 0x73 - 上传图像数据内容
                self.cmd_73_counter += 1  # 递增0x73命令计数器
                # 显示0x73命令基本统计信息
                self.log(f"收到0x73命令 #{self.cmd_73_counter}")
                 
                # 直接使用0x73命令的参数内容作为图像数据
                if param_length > 0:
                    # 提取参数内容 - 跳过CRC部分，从实际数据区开始提取
                    # 协议格式：aa(1字节) + 0x73(1字节) + 长度(2字节) + CRC(4字节) + 数据区
                    image_data_chunk = full_frame[8:8+param_length]  # 跳过帧头(1)+命令(1)+长度(2)+CRC(4)=8字节，提取实际数据
                    
                    # 保存原始数据到txt文件，每包区分开
                    #self._save_raw_data_to_txt(image_data_chunk, self.cmd_73_counter)
                    
                    # 直接将原始数据添加到累积数据中
                    self.current_image_data.extend(image_data_chunk)
                    self.image_total_chunks += 1
                    self.log(f"已添加原始图像数据块 #{self.image_total_chunks}，大小：{len(image_data_chunk)}字节")
                    self.log(f"当前累积图像数据总长度：{len(self.current_image_data)}字节")
                    
                    # 检查数据块是否不是1024字节长度，如果不是则保存图像
                    if len(image_data_chunk) != 1024:
                        self.log(f"🔍 检测到非标准长度数据块（{len(image_data_chunk)}字节），这通常表示图像数据传输结束")
                        self.log("📝 开始保存累积的图像数据...")
                        
                        # 调用保存方法
                        self._auto_save_image_data()
                        
                        # 重置计数器，准备接收新的图像数据
                        self.cmd_73_counter = 0
                        self.log("✅ 已重置计数器，准备接收新的图像数据")
                    else:
                        # 如果是标准长度数据块，继续累积
                        self.log("继续累积原始图像数据...")
                    
                    # 注意：保留所有原始数据，包括可能的前导0或其他标记
                else:
                    self.log("0x73命令参数长度为0，无图像数据")
            
            elif cmd == 0x74:  # 0x74命令 - 现在不再需要
                self.log("收到0x74命令，但根据需求不再处理")
                
            # 检查帧内容是否包含重启提示相关信息
            if param_length > 0:
                # 提取参数内容进行分析
                param_content = full_frame[4:4+param_length]  # 帧头(1) + cmd(1) + param_length(2) 后的内容
                
                # 尝试将参数内容转换为字符串进行检查
                try:
                    param_str = param_content.decode('ascii', errors='ignore')
                    # 检查是否包含重启相关关键词
                    if '重启' in param_str or 'restart' in param_str.lower():
                        self.is_restart_required = True
                        self.root.after(0, lambda: messagebox.showinfo("提示", "请重启板子"))
                        self.log(f"检测到重启提示: {param_str}")
                except Exception as e:
                    self.log(f"解析响应内容时出错: {str(e)}")
    
    def log(self, message):
        """添加日志信息"""
        self.log_text.insert(tk.END, message + "\n")
        self.log_text.see(tk.END)
    
    def clear_log(self):
        """清空日志内容"""
        self.log_text.delete(1.0, tk.END)
        self.log("日志已清空")
    
    def _auto_save_image_data(self):
        """自动保存累积收集的所有0x73命令数据为一张完整JPG文件"""
        self.log(f"🔍 _auto_save_image_data 方法被调用，当前数据长度：{len(self.current_image_data)}字节")
        
        if len(self.current_image_data) == 0:
            self.log("警告：没有累积的图像数据可保存")
            return
        
        # 直接保存数据，不再进行复杂的格式验证
        try:
            import os
            # 生成文件名
            timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S_%f")
            filename = f"image_capture_{timestamp}.jpg"
            
            # 确保保存目录存在
            save_dir = self.save_dir
            os.makedirs(save_dir, exist_ok=True)
            
            # 完整文件路径
            full_path = os.path.join(save_dir, filename)
            
            # 直接保存所有累积的数据
            with open(full_path, 'wb') as f:
                written_bytes = f.write(self.current_image_data)
            
            # 验证文件保存成功
            if os.path.exists(full_path) and os.path.getsize(full_path) > 0:
                file_size = os.path.getsize(full_path)
                self.log(f"✅ 图像保存成功：{full_path} ({file_size}字节)")
                self.log(f"✅ 合并了 {self.image_total_chunks} 个数据块")
                self.image_saved_flag = True
            else:
                self.log(f"⚠️  图像保存失败，尝试使用调试保存方法")
                self._auto_save_image_data_debug()
        except Exception as e:
            self.log(f"❌ 保存图像时出错: {str(e)}")
            # 出错时使用调试方法保存
            try:
                self._auto_save_image_data_debug()
            except Exception as debug_e:
                self.log(f"❌ 调试保存也失败: {str(debug_e)}")
        
        # 清空数据，为下一次收集做准备
        self.current_image_data = bytearray()
        self.image_total_chunks = 0
        self.image_saved_flag = False
        self.log("已清空累积数据，准备接收新的图像数据")
    
    def _save_raw_data_to_txt(self, data_chunk, chunk_number):
        """将原始0x73数据块保存到txt文件"""
        try:
            import os
            # 生成基础文件名（包含日期和时间，但不包含微秒）
            base_timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
            txt_filename = f"raw_data_{base_timestamp}.txt"
            txt_path = os.path.join(self.save_dir, txt_filename)
            
            # 将字节数据转换为十六进制字符串表示，便于查看
            hex_data = ' '.join([f"{byte:02X}" for byte in data_chunk])
            
            # 使用追加模式打开文件，这样可以保存所有数据包
            with open(txt_path, 'a', encoding='utf-8') as f:
                # 添加数据包分隔符和标识信息
                f.write(f"\n===== 数据包 #{chunk_number} (大小: {len(data_chunk)}字节) =====\n")
                f.write(f"时间戳: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}\n")
                
                # 将十六进制数据分成每行32个字节（64个十六进制字符加空格）
                for i in range(0, len(hex_data), 96):  # 32字节 * 3字符/字节 = 96
                    f.write(hex_data[i:i+96] + '\n')
                
                f.write("=============================\n")
            
            # 只在第一次保存或每个新文件创建时记录日志，避免日志过于冗余
            if chunk_number == 1:
                self.log(f"📝 开始记录原始数据到txt文件：{txt_filename}")
                
        except Exception as e:
            self.log(f"❌ 保存原始数据到txt文件时出错: {str(e)}")
    
    def _auto_save_image_data_debug(self):
        """调试用的图像保存方法，确保数据一定会被保存"""
        if len(self.current_image_data) == 0:
            self.log("调试保存：没有数据可保存")
            return
        
        try:
            # 生成调试文件名
            import os
            timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S_%f")
            debug_filename = f"debug_capture_{timestamp}.jpg"
            debug_path = os.path.join(self.save_dir, debug_filename)
            
            self.log(f"📁 调试保存：准备保存到 {debug_path}")
            
            # 强制保存，不做任何格式验证
            with open(debug_path, 'wb') as f:
                written = f.write(self.current_image_data)
            
            self.log(f"✅ 调试保存完成：写入 {written} 字节到 {debug_filename}")
            self.image_saved_flag = True  # 标记为已保存，避免重复调试保存
            
        except Exception as e:
            self.log(f"❌ 调试保存出错：{str(e)}")
    
    def save_collected_image(self):
        """保留该方法以保持兼容性，但实际上不再使用，图像会自动保存"""
        pass

if __name__ == "__main__":
    root = tk.Tk()
    app = SerialCommunicator(root)
    
    # 窗口关闭时断开连接
    def on_closing():
        if app.is_connected:
            app.disconnect_serial()
        root.destroy()
    
    root.protocol("WM_DELETE_WINDOW", on_closing)
    root.mainloop()